package uml.FileIO;

import java.awt.Component;
import java.awt.Container;
import java.awt.Point;
import java.awt.Stroke;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.LinkedList;

import javax.swing.text.JTextComponent;

import uml.GUI.DiagramPanel;
import uml.eventsystem.events.UMLEventId;
import uml.objects.AnchorPoint;
import uml.objects.ArrowEnd;
import uml.objects.UMLObject;
import uml.util.UMLConstants;

public class FileGenerator
{
	private ArrayList<UMLObject> m_objects;
	private int m_objectCount = 0;
	private int m_anchorPointCount = 0;

	private FileGenerator ()
	{
		m_objects = new ArrayList<UMLObject> ();
	}
	
	/**
	 * Saves the diagram to the file at location of fileName
	 * @param fileName
	 * @param diagram
	 */

	public static void saveDiagram (String fileName, DiagramPanel diagram)
	{
		File file = new File (fileName + ".ateam");
		PrintStream stream = null;
		try
		{
			stream = new PrintStream (file);
		}
		catch (IOException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace ();
		}
		FileGenerator fileGenerator = new FileGenerator();
		for (Component c : diagram.getComponents ())
			if (c instanceof UMLObject)
				fileGenerator.m_objects.add((UMLObject) c);
		for(UMLObject object : fileGenerator.m_objects)
			fileGenerator.saveObject (stream, object);

		stream.close ();
	}
	
	/**
	 * Saves umlObject to file
	 * @param file
	 * @param umlObject
	 */

	public void saveObject (PrintStream file, UMLObject umlObject)
	{
		try
		{
			if (umlObject.getEventId ().is(UMLEventId.CLASS_FACTORY))
				writeClassBlock (file, umlObject);
			else if (umlObject.getEventId ().is(UMLEventId.ARROW_FACTORY))//ignores non head anchor points
				writeArrow (file, umlObject);
		}
		catch (IOException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace ();
		}
	}
	
	/**
	 * writes a block of text to the file
	 * @param file
	 * @param umlObject
	 * @throws IOException
	 */

	private void writeClassBlock (PrintStream file, UMLObject umlObject)
			throws IOException
	{
		writeBeginLine (file);
		writeClassType (file);
		writeEventIdLine (file, umlObject.getEventId ());
		writeLocationLine (file, umlObject.getComponent ().getLocation ());
		for (int i = 0; i < getTextCount (umlObject); ++i)
			writeTextLine (file, getText (umlObject, i));
		writeEndLine (file);
	}
	
	/**
	 * Writes block of text for saving arrow to the file
	 * @param file
	 * @param umlObject
	 * @throws IOException
	 */
	
	private void writeArrow(PrintStream file, UMLObject umlObject) throws IOException
	{
		LinkedList<AnchorPoint> anchorPoints = new LinkedList<AnchorPoint>();
		anchorPoints.add((AnchorPoint) umlObject);
		while(anchorPoints.size() != 0)
		{
			writeAnchorPointBlock(file, anchorPoints.peek());
			for(AnchorPoint sibling : anchorPoints.remove().getSiblings())
				anchorPoints.add(sibling);
		}
	}
	
	/**
	 * Writes a block of text to file file to save umlObject
	 * @param file
	 * @param umlObject
	 * @throws IOException
	 */

	private void writeAnchorPointBlock (PrintStream file, UMLObject umlObject)
			throws IOException
	{
		writeBeginLine (file);
		writeAnchorType (file);
		AnchorPoint anchor = (AnchorPoint) umlObject;
		writeEventIdLine (file, anchor.getEventId ());
		writeLocationLine (file, anchor.getPoint ());
		writeContainerLine (file, anchor.getContext ());
		writeSiblingLine (file, anchor.getSiblings ());
		writeArrowEndLine(file, anchor.getArrowEnd());
		writeStrokeLine(file, anchor.getStroke());
		writeEndLine (file);
	}
	
	/**
	 * writes the Token used to create the Arrow to file
	 * @param file
	 */

	private void writeAnchorType (PrintStream file)
	{
		file.print (FileToken.ANCHOR.lexeme + " ");
		file.println ();
	}
	
	/**
	 * writes the token used to make the class box to file
	 * @param file
	 */

	private void writeClassType (PrintStream file)
	{
		file.print (FileToken.CLASS.lexeme + " ");
		file.println ();
	}
	
	/**
	 * Writes a Token to represent the EndType on this AnchorPoint
	 * @param file
	 * @throws IOException
	 */

	private void writeEndLine (PrintStream file)
			throws IOException
	{
		file.print (FileToken.END.lexeme + " ");
		file.print(m_objectCount++ + " ");
		file.println ("\n");
	}
	
	/**
	 * Writes begin token to file
	 * @param file
	 * @throws IOException
	 */

	private void writeBeginLine (PrintStream file)
			throws IOException
	{
		file.print (FileToken.BEGIN.lexeme + " ");
		file.print(m_objectCount);
		file.println ('\n');
	}
	
	/**
	 * Writes a class boxes text
	 * @param file
	 * @param text
	 * @throws IOException
	 */

	private void writeTextLine (PrintStream file, String text)
			throws IOException
	{
		text = text.replace ("\n", " \n" +FileToken.NEWLINE.lexeme + " " );
		file.print (FileToken.TEXT.lexeme + " ");
		file.print (text + " ");
		file.println ("\n");
	}
	
	/**
	 * Write the location of an object to file
	 * @param file
	 * @param location
	 * @throws IOException
	 */

	private void writeLocationLine (PrintStream file, Point location)
			throws IOException
	{
		file.print (FileToken.LOCATION.lexeme + " ");
		file.print (location.x + " ");
		file.println (" " + location.y + " ");
	}
	
	/**
	 * Writes the eventId of the object to the file
	 * @param file
	 * @param eventId
	 * @throws IOException
	 */

	private void writeEventIdLine (PrintStream file, UMLEventId eventId)
			throws IOException
	{
		file.print (FileToken.EVENTID.lexeme + " ");
		file.print (eventId + " ");
		file.println ("\n");
	}
	
	/**
	 * Writes the siblings of an object to file
	 * @param file
	 * @param siblings
	 * @throws IOException
	 */

	private void writeSiblingLine (PrintStream file, ArrayList<AnchorPoint> siblings)
			throws IOException
	{
		file.print (FileToken.SIBLINGS.lexeme + " ");
		file.print (lookup (siblings) + " ");
		file.println ("\n");
	}
	
	/**
	 * Writes the objects Context to file
	 * @param file
	 * @param context
	 * @throws IOException
	 */

	private void writeContainerLine (PrintStream file, Container context)
			throws IOException
	{
		file.print (FileToken.CONTAINER.lexeme + " ");
		if(context instanceof UMLObject)
			file.print (lookup ((UMLObject) context) + " ");
		else
		file.print("null" + " ");
		file.println ("\n");
	}
	
	/**
	 * Writes the ArrowEnd to file
	 * @param file
	 * @param arrowEnd
	 */
	
	private void writeArrowEndLine(PrintStream file, ArrowEnd arrowEnd)
	{
		file.print(getArrowEndToken(arrowEnd).lexeme + " ");
		file.println();
	}
	
	/**
	 * Writes the Stroke token of the object to file
	 * @param file
	 * @param stroke
	 */
	
	private void writeStrokeLine(PrintStream file, Stroke stroke)
	{
		file.print(getStrokeToken(stroke).lexeme + " ");
		file.println();
	}
	
	/**
	 * Looks up the index of a umlObject
	 * @param umlObject
	 * @return
	 */

	private int lookup (UMLObject umlObject)
	{
		return m_objects.indexOf (umlObject);
	}
	
	/**
	 * Looks up the object and returns a String with the index
	 * @param umlObjects
	 * @return
	 */

	private String lookup (ArrayList<AnchorPoint> umlObjects)
	{
		String indexes = "";
		for (UMLObject umlObject : umlObjects)
			indexes += ++m_anchorPointCount;
		return indexes;
	}
	
	/**
	 * Gets the text out of the object's child at index i
	 * @param umlObject
	 * @param i
	 * @return
	 */

	private String getText (UMLObject umlObject, int i)
	{
		ArrayList<JTextComponent> textAreas = getJTextComponents (umlObject.getComponent ());
		return textAreas.get (i).getText ();
	}
	
	/**
	 * Finds the amount of JTextAreas in the object
	 * @param umlObject
	 * @return
	 */

	private int getTextCount (UMLObject umlObject)
	{
		return getJTextComponents (umlObject.getComponent ()).size ();
	}
	
	/**
	 * Returns all the JTextComponents of an object
	 * @param component
	 * @return
	 */

	private ArrayList<JTextComponent> getJTextComponents (Component component)
	{
		ArrayList<JTextComponent> textAreas = new ArrayList<JTextComponent> ();
		if (component instanceof Container)
		{
			Container container = (Container) component;
			for (Component c : container.getComponents ())
				if (c instanceof JTextComponent)
					textAreas.add ((JTextComponent) c);
		}
		return textAreas;
	}
	
	/**
	 * Returns the token associated to the arrowend
	 * @param arrowEnd
	 * @return
	 */
	
	private FileToken getArrowEndToken(ArrowEnd arrowEnd)
	{
		if(arrowEnd == null)
			return FileToken.NULL;
		if(arrowEnd.equals(UMLConstants.BLACK_ARROW_HEAD))
			return FileToken.BLACKHEAD;
		else if(arrowEnd.equals(UMLConstants.WHITE_ARROW_HEAD))
			return FileToken.WHITEHEAD;
		else if(arrowEnd.equals(UMLConstants.BLACK_DIAMOND))
			return FileToken.BLACKDIAMOND;
		else if(arrowEnd.equals(UMLConstants.WHITE_DIAMOND))
			return FileToken.WHITEDIAMOND;
		else
			return FileToken.NULL;
	}
	
	/**
	 * Returns the Token associated with the Stroke
	 * @param stroke
	 * @return
	 */
	
	private FileToken getStrokeToken(Stroke stroke)
	{
		if (stroke.equals(UMLConstants.REGULAR_LINE))
			return FileToken.REGULAR_LINE;
		else if(stroke.equals(UMLConstants.DASHED_LINE))
			return FileToken.DASHED_LINE;
		else
			return FileToken.NULL;
	}
}
